package com.googlecode.icecube.push.game;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Map.Entry;

import org.springframework.beans.factory.annotation.Autowired;

import com.googlecode.icefusion.ui.commons.BackingBeanForm;
import com.googlecode.icefusion.ui.commons.constant.Context;
import com.googlecode.icefusion.ui.commons.constant.ICEfusionConsts;
import com.icesoft.faces.async.render.SessionRenderer;


/**
 * Manages global status of different player and their dependencies to each other.
 * 
 * @author Rainer Eschen
 * 
 */
public class MultiUserGame extends BackingBeanForm {

    @Autowired
    private ICEfusionConsts consts;

    @Autowired
    private Context context;

    /**
     * Context for session renderer output
     */
    private String renderGroup = "ICEmapper";

    /**
     * Number of countries a new player gets.
     */
    private final static int startingCountries = 5;

    /**
     * Fixed list of available countries to play with.
     * 
     * @see "http://en.wikipedia.org/wiki/Coverage_details_of_Google_Maps"
     */
    List<String> countries = new ArrayList<String>(Arrays.asList("Afghanistan", "Andorra", "Antigua and Barbuda",
        "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Barbados", "Belarus",
        "Belgium", "Belize", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Brazil", "Brunei", "Bulgaria", "Cambodia",
        "Canada", "Chile", "China", "Colombia", "Costa Rica", "Croatia", "Cuba", "Czech Republic", "Denmark",
        "Ecuador", "El Salvador", "Estonia", "Finland", "France", "Germany", "Gibraltar", "Greece", "Grenada",
        "Guatemala", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq",
        "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Kazakhstan", "Kyrgyzstan", "Latvia", "Liechtenstein",
        "Lithuania", "Luxembourg", "Macau", "Macedonia", "Malaysia", "Mauritius", "Mexico", "Moldova", "Monaco",
        "Mongolia", "Montenegro", "Myanmar", "Netherlands", "New Zealand", "Nicaragua", "North Korea", "Norway",
        "Oman", "Pakistan", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal",
        "Reunion", "Romania", "Russia", "Saint Kitts and Nevis", "Saint Lucia", "San Marino", "Saudi Arabia", "Serbia",
        "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sri Lanka", "Sweden",
        "Switzerland", "Taiwan", "Tajikistan", "Thailand", "Trinidad and Tobago", "Turkey", "Turkmenistan", "Ukraine",
        "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam",
        "Yemen"));

    /**
     * List of available countries for new players.
     */
    List<String> freeCountries = new ArrayList<String>(this.countries);

    /**
     * List of current players.
     */
    List<Player> players = new ArrayList<Player>();

    /**
     * Start a new game and reset everything that exists.
     */
    public void startGame() {

        this.players.clear();
        this.freeCountries = new ArrayList<String>(this.countries);
    }

    /**
     * Put a new player into the game.
     * 
     * @param name Player name
     * @exception NotEnoughCountriesException Not enough countries left to let the player in.
     * @return player reference in the game
     */
    public Player addPlayer(String name) {
        name = this.checkDoubles(name);
        Player player = new Player();
        player.setName(name);
        for (int i = 0; i < MultiUserGame.startingCountries; i++) {
            player.getCountries().put(this.setRandomCountry(player), null);
        }
        players.add(player);
        SessionRenderer.addCurrentSession(this.renderGroup);
        return player;
    }

    /**
     * Player leaves the game.
     * 
     * @param currentPlayer current player
     */
    public void deletePlayer(Player currentPlayer) {

        int index = this.players.indexOf(currentPlayer);
        if (index > -1) {
            this.players.remove(index);
            for (Player player : this.players) {
                for (Entry<String, Player> country : player.getCountries().entrySet()) {
                    if (country.getValue() != null && country.getValue().equals(currentPlayer)) {
                        country.setValue(null);
                    }
                }
            }
        }
    }

    /**
     * Calculate if player has lost the game.
     * 
     * @param currentPlayer current player
     * @return true: player has lost
     */
    public Boolean getLoser(Player currentPlayer) {

        Boolean loser = false;
        int index = this.players.indexOf(currentPlayer);
        if (index > -1) {
            loser = !players.get(index).getCountries().values().contains(null);
        }
        return loser;
    }

    /**
     * Search for hit in other player's country lists.
     * 
     * @param currentPlayer Who made the suggestion?
     * @param countryName What was suggested?
     * @return found player instance
     */
    public Player checkCountry(Player currentPlayer, String countryName) {

        for (Player player : players) {
            if (!player.equals(currentPlayer)) {
                for (Entry<String, Player> country : player.getCountries().entrySet()) {
                    // The used country list doesn't match the names the Map is delivering.
                    // So, we do substring matching in both direction.
                    countryName = countryName.toLowerCase();
                    String entryCountryName = country.getKey().toLowerCase();
                    if ((countryName.indexOf(entryCountryName) > -1 || entryCountryName.indexOf(countryName) > -1)
                            && (country.getValue() == null)) {
                        player.getCountries().put(country.getKey(), currentPlayer);
                        return player;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Delivers List of Country Markers for the Google Map. The ui:repeat can only manage lists, so we take a Map.Entry
     * as element to avoid an extra object.
     * 
     * @param currentPlayer current player
     * @return list of country markers
     */
    public List<Map.Entry<String, String>> getCountriesMap(Player currentPlayer) {
        Map<String, String> countries = new HashMap<String, String>();
        for (Player player : this.players) {
            if (player.equals(currentPlayer)) {
                for (Entry<String, Player> country : player.getCountries().entrySet()) {
                    if (country.getValue() == null) {
                        countries.put(country.getKey(), this.consts.getContextPath() + this.consts.getSkinBase() + "/"
                                + this.context.getSkin() + "/images/map/standard.png");
                    } else {
                        countries.put(country.getKey(), this.consts.getContextPath() + this.consts.getSkinBase() + "/"
                                + this.context.getSkin() + "/images/map/standardHit.png");
                    }
                }
            } else {
                for (Entry<String, Player> country : player.getCountries().entrySet()) {
                    if (country.getValue() != null && country.getValue().equals(currentPlayer)) {
                        countries.put(country.getKey(), this.consts.getContextPath() + this.consts.getSkinBase() + "/"
                                + this.context.getSkin() + "/images/map/hit.png");
                    }
                }
            }
        }
        List<Map.Entry<String, String>> countriesList = new ArrayList<Map.Entry<String, String>>(countries.entrySet());
        return countriesList;
    }

    /**
     * Delivers a country status list of the current player.
     * 
     * @param player current player
     * @return list of countries and their hits
     */
    public String getCountries(Player player) {
        String countriesList = "";
        int index = players.indexOf(player);
        if (index == -1) {
            return countriesList;
        }
        Map<String, Player> countries = players.get(index).getCountries();
        for (Entry<String, Player> country : countries.entrySet()) {
            countriesList += country.getKey();
            if (country.getValue() != null) {
                countriesList += "[" + country.getValue().getName() + "]";
            }
            countriesList += ", ";
        }
        String returnString = (countriesList.length() < 2) ? countriesList : countriesList.substring(0, countriesList
                .length() - 2);
        return returnString;
    }

    /**
     * Delivers a list of countries by other players that were hit by the current player.
     * 
     * @param currentPlayer current player
     * @return list of countries that were hit
     */
    public String getCountriesHit(Player currentPlayer) {

        String hitList = "";
        for (Player player : players) {
            if (!player.equals(currentPlayer)) {
                hitList += player.getName();
                if (player.getCountries().values().contains(currentPlayer)) {
                    hitList += "[";
                    for (Entry<String, Player> country : player.getCountries().entrySet()) {
                        if (country.getValue() != null && currentPlayer.equals(country.getValue())) {
                            hitList += country.getKey() + ", ";
                        }
                    }
                    hitList = hitList.substring(0, hitList.length() - 2);
                    hitList += "]";
                }
                hitList += ", ";
            }
        }
        return (hitList.length() < 2) ? hitList : hitList.substring(0, hitList.length() - 2);
    }

    /**
     * Delivers a list of players which all countries are already hit.
     * 
     * @param currentPlayer current player
     * @return list of losers
     */
    public String getLosers(Player currentPlayer) {

        String losersList = "";
        for (Player player : players) {
            if (!player.equals(currentPlayer) && !player.getCountries().values().contains(null)) {
                losersList += player.getName() + ", ";
            }
        }
        return (losersList.length() < 2) ? losersList : losersList.substring(0, losersList.length() - 2);
    }

    /**
     * Player names have to be unique. Check for doubles and create a different name.
     * 
     * @param name Player Name
     * @return processed Player name
     */
    protected String checkDoubles(String name) {

        Long number = 1L;
        String newName = name;
        for (Player player : players) {
            if (player.getName().equals(newName)) {
                newName = name + number.toString();
                number++;
            }
        }
        return newName;
    }

    protected String setRandomCountry(Player player) {

        if (this.freeCountries.size() < 1) {
            throw new NotEnoughCountriesException("Free Countries list is empty for [" + player.getName() + "].");
        }
        Random generator = new Random();
        int index = generator.nextInt(this.freeCountries.size());
        String country = new String(this.freeCountries.get(index));
        this.freeCountries.remove(index);
        return country;
    }

    public void push() {
        SessionRenderer.render(this.renderGroup);
    }
}
